/**
 * Copyright 2013 yangming.liu<liuyangming@gmail.com>.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, see <http://www.gnu.org/licenses/>.
 */
package org.bytesoft.openjta.resource;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.transaction.HeuristicCommitException;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;

import org.bytesoft.openjta.archive.XAResourceArchive;
import org.bytesoft.openjta.internal.RollbackRequiredException;
import org.bytesoft.openjta.internal.TransactionVote;
import org.bytesoft.openjta.supports.TransactionLogger;
import org.bytesoft.openjta.xa.XidImpl;
import org.bytesoft.utils.ByteUtils;

public abstract class AbstractLocalTerminator implements Terminator {
	protected final List<XAResourceArchive> allXAResourceArchives = new ArrayList<XAResourceArchive>();
	protected final List<XAResourceArchive> enlistedXAResourceArchives = new ArrayList<XAResourceArchive>();
	protected final List<XAResourceArchive> delistedXAResourceArchives = new ArrayList<XAResourceArchive>();
	protected final List<XAResourceArchive> suspendedXAResourceArchives = new ArrayList<XAResourceArchive>();

	protected TransactionLogger transactionLogger;

	public void xaCommit() throws HeuristicMixedException, HeuristicRollbackException, RollbackException,
			SecurityException, SystemException, RemoteException {
		boolean errorExists = false;
		boolean mixedExists = false;
		boolean rollbackExists = false;
		boolean commitExists = false;

		Iterator<XAResourceArchive> itr = this.delistedXAResourceArchives.iterator();
		while (itr.hasNext()) {
			boolean commitRequired = true;
			XAResourceArchive archive = itr.next();
			if (archive.isRollbackSuccess()) {
				rollbackExists = true;
				commitRequired = false;
			} else if (archive.isCommitSuccess()) {
				commitExists = true;
				commitRequired = false;
			} else if (archive.isCommitFailure()) {
				if (archive.isHeuristicExists()) {
					commitRequired = false;

					int errcode = archive.getHeuristicCode();
					if (errcode == XAException.XA_HEURCOM) {
						commitExists = true;
					} else if (errcode == XAException.XA_HEURRB) {
						rollbackExists = true;
					} else if (errcode == XAException.XA_HEURHAZ) {
						mixedExists = false;
					} else if (errcode == XAException.XA_HEURMIX) {
						mixedExists = false;
					} else {
						// never happen
						errorExists = true;
					}
				} else {
					errorExists = true;
				}
			} else if (archive.isPrepareCompleted() && archive.getResourceVote() == TransactionVote.XA_RDONLY) {
				commitRequired = false;
			}

			if (commitRequired) {
				boolean success = false;
				try {
					XAResource xaRes = archive.getXAResource();
					XidImpl branchXid = archive.getBranchXid();
					System.out.printf(String.format("\t[%s] commit: xares= %s%n",
							ByteUtils.byteArrayToString(branchXid.getBranchQualifier()), xaRes));
					archive.setCommitStarted(true);
					this.transactionLogger.enterCommitXAResource(archive);
					xaRes.commit(branchXid, false);
					success = true;
					commitExists = true;
				} catch (XAException xae) {
					xae.printStackTrace();

					// Possible XAExceptions are XA_HEURHAZ, XA_HEURCOM,
					// XA_HEURRB, XA_HEURMIX, XAER_RMERR, XAER_RMFAIL,
					// XAER_NOTA, XAER_INVAL, or XAER_PROTO.
					switch (xae.errorCode) {
					case XAException.XA_HEURHAZ:
					case XAException.XA_HEURMIX:
						archive.setHeuristicExists(true);
						archive.setHeuristicCode(xae.errorCode);
						mixedExists = true;
						break;
					case XAException.XA_HEURCOM:
						success = true;
						commitExists = true;
						break;
					case XAException.XAER_RMERR:
					case XAException.XAER_RMFAIL:
						errorExists = true;
						break;
					case XAException.XAER_NOTA:
					case XAException.XAER_INVAL:
					case XAException.XAER_PROTO:
						// ignore
						break;
					case XAException.XA_HEURRB:
						archive.setHeuristicExists(true);
						archive.setHeuristicCode(xae.errorCode);
					default:
						rollbackExists = true;
					}
				} catch (RuntimeException ex) {
					errorExists = true;
					ex.printStackTrace();
				} finally {
					archive.setCommitResult(success);
					this.transactionLogger.completeCommitXAResource(archive);
				}
			}
		}

		if (mixedExists) {
			throw new HeuristicMixedException();
		} else if (commitExists && rollbackExists) {
			throw new HeuristicMixedException();
		} else if (errorExists) {
			throw new SystemException();
		} else {
			if (rollbackExists) {
				throw new HeuristicRollbackException();
			}
		}
	}

	public void xaRollback() throws HeuristicMixedException, HeuristicCommitException, SystemException, RemoteException {
		boolean errorExists = false;
		boolean mixedExists = false;
		boolean commitExists = false;
		boolean rollbackExists = false;

		Iterator<XAResourceArchive> delistedItr = this.delistedXAResourceArchives.iterator();
		while (delistedItr.hasNext()) {
			boolean rollbackRequired = true;
			XAResourceArchive archive = delistedItr.next();
			if (archive.isCommitSuccess()) {
				commitExists = true;
				rollbackRequired = false;
			} else if (archive.isRollbackSuccess()) {
				rollbackExists = true;
				rollbackRequired = false;
			} else if (archive.isRollbackFailure()) {
				if (archive.isHeuristicExists()) {
					rollbackRequired = false;

					int errcode = archive.getHeuristicCode();
					if (errcode == XAException.XA_HEURCOM) {
						commitExists = true;
					} else if (errcode == XAException.XA_HEURRB) {
						rollbackExists = true;
					} else if (errcode == XAException.XA_HEURHAZ) {
						mixedExists = false;
					} else if (errcode == XAException.XA_HEURMIX) {
						mixedExists = false;
					} else {
						// never happen
						errorExists = true;
					}
				} else {
					errorExists = true;
				}
			} else if (archive.isPrepareCompleted() && archive.getResourceVote() == TransactionVote.XA_RDONLY) {
				rollbackRequired = false;
			}

			if (rollbackRequired) {
				boolean success = false;
				try {
					XAResource xaRes = archive.getXAResource();
					XidImpl branchXid = archive.getBranchXid();
					System.out.printf(String.format("\t[%s] rollback: xares= %s%n",
							ByteUtils.byteArrayToString(branchXid.getBranchQualifier()), xaRes));
					archive.setRollbackStarted(true);
					this.transactionLogger.enterRollbackXAResource(archive);
					xaRes.rollback(branchXid);
					rollbackExists = true;
					success = true;
				} catch (XAException xae) {
					// Possible XAExceptions are XA_HEURHAZ, XA_HEURCOM,
					// XA_HEURRB, XA_HEURMIX, XAER_RMERR, XAER_RMFAIL,
					// XAER_NOTA, XAER_INVAL, or XAER_PROTO.
					switch (xae.errorCode) {
					case XAException.XA_HEURMIX:
					case XAException.XA_HEURHAZ:
						archive.setHeuristicExists(true);
						archive.setHeuristicCode(xae.errorCode);
						mixedExists = true;
						break;
					case XAException.XA_HEURCOM:
						archive.setHeuristicExists(true);
						archive.setHeuristicCode(xae.errorCode);
						commitExists = true;
						break;
					case XAException.XA_HEURRB:
						rollbackExists = true;
						success = true;
						break;
					case XAException.XAER_NOTA:
					case XAException.XAER_INVAL:
					case XAException.XAER_PROTO:
						// ignore
						break;
					case XAException.XAER_RMERR:
					case XAException.XAER_RMFAIL:
						errorExists = true;
					}
				} catch (RuntimeException ex) {
					errorExists = true;
				} finally {
					archive.setRollbackResult(success);
					this.transactionLogger.completeRollbackXAResource(archive);
				}
			}
		}

		if (mixedExists) {
			throw new HeuristicMixedException();
		} else if (commitExists && rollbackExists) {
			throw new HeuristicMixedException();
		} else if (errorExists) {
			throw new SystemException();
		} else {
			if (commitExists) {
				throw new HeuristicCommitException();
			}
		}
	}

	public TransactionVote xaPrepare() throws SystemException, RollbackRequiredException, RemoteException {
		int[] resourceVotes = new int[this.delistedXAResourceArchives.size()];

		for (int i = 0; i < this.delistedXAResourceArchives.size(); i++) {
			XAResourceArchive archive = this.delistedXAResourceArchives.get(i);
			if (archive.isPrepareSuccess()) {
				resourceVotes[i] = archive.getResourceVote();
			} else if (archive.isPrepareFailure()) {
				resourceVotes[i] = TransactionVote.XA_ROLLBACK_REQUIRED;
			} else {
				boolean success = false;
				try {
					XAResource xares = archive.getXAResource();
					XidImpl branchXid = archive.getBranchXid();

					System.out.printf(String.format("\t[%s] prepare: xares= %s%n",
							ByteUtils.byteArrayToString(branchXid.getBranchQualifier()), xares));

					archive.setPrepareStarted(true);
					this.transactionLogger.enterPrepareXAResource(archive);
					resourceVotes[i] = xares.prepare(branchXid);
					success = true;
				} catch (XAException xae) {
					// Possible exception values are: XA_RB*, XAER_RMERR,
					// XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or XAER_PROTO.
					switch (xae.errorCode) {
					case XAException.XAER_RMERR:
					case XAException.XAER_RMFAIL:
					case XAException.XAER_NOTA:
					case XAException.XAER_INVAL:
					case XAException.XAER_PROTO:
					default:

						resourceVotes[i] = TransactionVote.XA_ROLLBACK_REQUIRED;
					}
				} catch (RuntimeException e) {
					success = true;
					resourceVotes[i] = TransactionVote.XA_ROLLBACK_REQUIRED;
				} finally {
					archive.setPrepareResult(success);
					archive.setResourceVote(resourceVotes[i]);
					this.transactionLogger.completePrepareXAResource(archive);
				}
			}
		}

		TransactionVote globalVote = new TransactionVote(TransactionVote.XA_RDONLY);
		for (int i = 0; i < resourceVotes.length; i++) {
			int vote = resourceVotes[i];
			if (vote == XAResource.XA_RDONLY) {
				// ignore
			} else if (vote == XAResource.XA_OK) {
				globalVote.setValue(TransactionVote.XA_OK);
			} else {
				throw new SystemException();
			}
		}
		return globalVote;
	}

	public TransactionLogger getTransactionLogger() {
		return transactionLogger;
	}

	public void setTransactionLogger(TransactionLogger transactionLogger) {
		this.transactionLogger = transactionLogger;
	}

	public List<XAResourceArchive> getAllXAResourceArchives() {
		return allXAResourceArchives;
	}

	public List<XAResourceArchive> getEnlistedXAResourceArchives() {
		return enlistedXAResourceArchives;
	}

	public List<XAResourceArchive> getDelistedXAResourceArchives() {
		return delistedXAResourceArchives;
	}

	public List<XAResourceArchive> getSuspendedXAResourceArchives() {
		return suspendedXAResourceArchives;
	}

}
